#include <linux/udp.h>
#include <linux/if_ether.h>
#include <linux/netdevice.h>
#include <linux/igmp.h>
#include <net/route.h>
#include <net/ip.h>
#include <linux/timer.h>
#include <linux/in.h>
#include <linux/inet.h>
#include "socs/comm/config.h"
#include "socs/comm/socs.h"
#include "socs/mod/multicast.h"
#include "socs/mod/neigh.h"
#include "socs/mod/sap.h"
#include "socs/mod/hello.h"

#include <linux/timer.h>
#include <linux/timex.h>
#include <linux/rtc.h>


#if 0
#define PRINT_HERE printk("MAYC HERE: %s %d\n", __func__, __LINE__)
#else
#define PRINT_HERE
#endif

static struct multicast_info *socs_mul = NULL;
/* static struct task_struct *mul_task = NULL; */
static struct timer_list sendtimer;
unsigned int sendtimes = 0;
static struct workqueue_struct *mul_workqueue = NULL;

static DEFINE_SPINLOCK(multicast_lock);
/* static char multicast_recv_buf[65535]; */

struct multicast_info *socs_mul_get(void)
{
	return socs_mul;
}

static int multicast_sock_init(int family, struct locallink *lcl, void *mcaddr,
							   struct socket **sock)
{
	int ret;
	int on = 1, loopback = 0;
	struct ip_mreq mreq;
	struct ipv6_mreq mreq6;
	struct sockaddr_in bind_sa;
	struct sockaddr_in6 bind_sa6;
	struct sockaddr *p_sa;
	int sa_len;
	struct net_device *dev;
	int ifindex;
	struct in_addr ifaddr4;

	if (family != IPV4 && family != IPV6) {
		PRINT_HERE;
		return -1;
	}

	ret = sock_create_kern((family == IPV4) ? AF_INET : AF_INET6,
						   SOCK_DGRAM, 0, sock);

	if (ret) {
		PRINT_HERE;
		return -1;
	}

	ret = kernel_setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
							sizeof(on));

	if (ret) {
		PRINT_HERE;
		goto failed;
	}

	if (family == IPV6) {
		dev = dev_get_by_name(&init_net, lcl->name);

		if (!dev) {
			PRINT_HERE;
			goto failed;
		}

		ifindex = mreq6.ipv6mr_ifindex = dev->ifindex;
		dev_put(dev);
		mreq6.ipv6mr_multiaddr = *(struct in6_addr *)mcaddr;
		ret = kernel_setsockopt(*sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
								(char *)&mreq6, sizeof(mreq6));

		if (ret) {
			PRINT_HERE;
			goto failed;
		}

		ret = kernel_setsockopt(*sock, IPPROTO_IPV6, IPV6_MULTICAST_IF,
								(char *)&ifindex, sizeof(ifindex));

		if (ret) {
			PRINT_HERE;
			goto failed;
		}
	} else {
		ifaddr4 = mreq.imr_interface = lcl->address.ipv4;
		mreq.imr_multiaddr = *(struct in_addr *)mcaddr;
		ret = kernel_setsockopt(*sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
								(char *)&mreq, sizeof(mreq));

		if (ret) {
			PRINT_HERE;
			goto failed;
		}

		ret = kernel_setsockopt(*sock, IPPROTO_IP, IP_MULTICAST_IF,
								(char *)&ifaddr4, sizeof(ifaddr4));

		if (ret) {
			PRINT_HERE;
			goto failed;
		}
	}

	ret = kernel_setsockopt(*sock, family == IPV6 ? IPPROTO_IPV6 : IPPROTO_IP,
							family == IPV6 ? IPV6_MULTICAST_LOOP : IP_MULTICAST_LOOP,
							(char *)&loopback, sizeof(loopback));

	if (family == IPV6) {
		memset(&bind_sa6, 0, sizeof(bind_sa6));
		bind_sa6.sin6_family = AF_INET6;
		bind_sa6.sin6_port = htons(SPORT);
		bind_sa6.sin6_addr = lcl->address.ipv6;
		p_sa = (struct sockaddr *)&bind_sa6;
		sa_len = sizeof(bind_sa6);
	} else {
		memset(&bind_sa, 0, sizeof(bind_sa));
		bind_sa.sin_family = AF_INET;
		bind_sa.sin_port = htons(SPORT);
		bind_sa.sin_addr.s_addr = lcl->address.ipv4.s_addr;
		p_sa = (struct sockaddr *)&bind_sa;
		sa_len = sizeof(bind_sa);
	}

	ret = kernel_bind(*sock, p_sa, sa_len);

	if (ret < 0) {
		PRINT_HERE;
		goto failed;
	}

	return 0;
failed:

	if (*sock) {
		sock_release(*sock);
		*sock = NULL;
	}

	return -1;
}



int socs_mul_update(const char *ifname)
{
	struct locallink lcl;
	int ret;
	struct in_addr mcip4;
	struct in6_addr mcip6;
	struct socket *old_sock = NULL, *new_sock = NULL;
	int family;
	PRINT_HERE;
	family = socs_neigh_getlcl_unequal(IPV6, ifname, &lcl);
	PRINT_HERE;

	if (family < 0) {
		family = socs_neigh_getlcl_unequal(IPV4, ifname, &lcl);
	}

	PRINT_HERE;
	spin_lock_bh(&multicast_lock);

	if (family < 0) {
		PRINT_HERE;
		log_debug("there is no lcl left");
		socs_mul->family = -1;
		memset(socs_mul->ifname, 0, IFNAMSIZ);
		memcpy(socs_mul->ifname, "none", 4);
		memset(&socs_mul->multicast_addr, 0, sizeof(socs_mul->multicast_addr));

		if (socs_mul->sock) {
			old_sock = socs_mul->sock;
			socs_mul->sock = NULL;
		}

		spin_unlock_bh(&multicast_lock);
		ret = -1;
		goto out;
	}

	PRINT_HERE;

	if (family == socs_mul->family
		&& memcmp(&socs_mul->ifname, lcl.name, IFNAMSIZ) == 0) {
		PRINT_HERE;
		spin_unlock_bh(&multicast_lock);
		return 0;
	}

	if (socs_mul->sock) {
		old_sock = socs_mul->sock;
		socs_mul->sock = NULL;
	}

	socs_mul->family = -1;
	memset(socs_mul->ifname, 0, IFNAMSIZ);
	memcpy(socs_mul->ifname, "none", 4);
	memset(&socs_mul->multicast_addr, 0, sizeof(socs_mul->multicast_addr));
	memset(&socs_mul->local_addr, 0, sizeof(socs_mul->local_addr));
	spin_unlock_bh(&multicast_lock);
	PRINT_HERE;

	/* socs_mul->family = ret; */
	if (family == IPV6) {
		PRINT_HERE;
		socs_config_get(ARGP_MCIP6, &mcip6);
		socs_mul->multicast_addr.addr6 = mcip6;
		ret = multicast_sock_init(family, &lcl, &mcip6,
								  &new_sock);
	} else {
		PRINT_HERE;
		socs_config_get(ARGP_MCIP4, &mcip4);
		socs_mul->multicast_addr.addr4 = mcip4;
		ret = multicast_sock_init(family, &lcl, &mcip4,
								  &new_sock);
	}

	if (!ret) {
		int addrlen;
		int rc;

		if (family == IPV4) {
			struct sockaddr_in tmp4;
			addrlen = sizeof(tmp4);
			rc = kernel_getsockname(new_sock, (struct sockaddr *)&tmp4, &addrlen);
			spin_lock_bh(&multicast_lock);
			socs_mul->family = family;
			socs_mul->sock = new_sock;
			socs_mul->local_addr.addr4 = tmp4.sin_addr;
			memcpy(socs_mul->ifname, lcl.name, IFNAMSIZ);
			spin_unlock_bh(&multicast_lock);
		} else {
			struct sockaddr_in6 tmp6;
			addrlen = sizeof(tmp6);
			rc = kernel_getsockname(new_sock, (struct sockaddr *)&tmp6, &addrlen);
			spin_lock_bh(&multicast_lock);
			socs_mul->family = family;
			socs_mul->sock = new_sock;
			socs_mul->local_addr.addr6 = tmp6.sin6_addr;
			memcpy(socs_mul->ifname, lcl.name, IFNAMSIZ);
			spin_unlock_bh(&multicast_lock);
		}
	}

out:

	if (old_sock) {
		sock_release(old_sock);
	}

	PRINT_HERE;
	return ret;
}

typedef struct mul_work {
	struct work_struct work;
	char ifname[IFNAMSIZ];
} mul_work_t;

static void mul_work_function(struct work_struct *work)
{
	mul_work_t *mul_work = (mul_work_t *)work;

	if (*mul_work->ifname) {
		socs_mul_update(mul_work->ifname);
	} else {
		socs_mul_update(NULL);
	}

	kfree(mul_work);
}

void socs_mul_localnode_down(const char *ifname)
{
	if (socs_mul && memcmp(ifname, socs_mul->ifname, IFNAMSIZ) == 0) {
		//socs_mul_update(ifname);
		mul_work_t *mul_work = kmalloc(sizeof(*mul_work), GFP_ATOMIC);

		if (mul_work) {
			memcpy(mul_work->ifname, ifname, IFNAMSIZ);
			INIT_WORK((struct work_struct *)mul_work, mul_work_function);
			queue_work(mul_workqueue, (struct work_struct *)mul_work);
		}
	}
}

void socs_mul_localnode_up(const char *iframe)
{
	if (socs_mul && socs_mul->family != IPV6) {
		/* socs_mul_update(NULL); */
		mul_work_t *mul_work = kmalloc(sizeof(*mul_work), GFP_ATOMIC);

		if (mul_work) {
			memset(mul_work->ifname, 0, IFNAMSIZ);
			INIT_WORK((struct work_struct *)mul_work, mul_work_function);
			queue_work(mul_workqueue, (struct work_struct *)mul_work);
		}
	}
}


static void sendtimer_expire(unsigned long data)
{
	unsigned int tm_stable, tm_hello;
	sendtimes++;
	socs_config_get(ARGP_TMSTABLE, &tm_stable);
	socs_config_get(ARGP_TMHELLO, &tm_hello);

	if (sendtimes > (tm_stable / tm_hello)) {
		if (sendtimes % (tm_stable / tm_hello) == 0) {
			saptb_vote(saptb_get(IPV4));
			saptb_vote(saptb_get(IPV6));
		}

		socs_neigh_send_localnode();
	} else if (sendtimes < (tm_stable / tm_hello)) {
		socs_neigh_send_localnode();
	} else {
		saptb_vote(saptb_get(IPV4));
		saptb_vote(saptb_get(IPV6));
		socs_neigh_send_localnode();
		log_info(MODULE_NAME " is stable");
	}

	mod_timer(&sendtimer, jiffies + msecs_to_jiffies(tm_hello));
}


int multicast_init(void)
{
	unsigned int tm_hello;

	if (NULL == (mul_workqueue = create_workqueue("multicast wq"))) {
		log_err(ERR_ALLOC_FAILED, "%s socs_mul workqueue init failed", __func__);
		return -1;
	}

	if (NULL == (socs_mul = (struct multicast_info *)
							kmalloc(sizeof(struct multicast_info), GFP_KERNEL))) {
		log_err(ERR_ALLOC_FAILED, "%s kmalloc socs_mul failed", __func__);
		return -1;
	}

	memset(socs_mul, 0, sizeof(*socs_mul));
	socs_mul_update(NULL);
	setup_timer(&sendtimer, sendtimer_expire, 0);
	socs_config_get(ARGP_TMHELLO, &tm_hello);
	mod_timer(&sendtimer, jiffies + msecs_to_jiffies(tm_hello));
	sendtimes = 0;
	/* mul_task = kthread_run(multicast_thread_function, NULL, "sock_multicast"); */
	/* if(IS_ERR(mul_task)) {                                                     */
	/*     mul_task = NULL;                                                       */
	/*     PRINT_HERE;                                                            */
	/* }                                                                          */
	return 0;
}

void multicast_destroy(void)
{
	del_timer(&sendtimer);

	if (mul_workqueue) {
		destroy_workqueue(mul_workqueue);
	}

	if (socs_mul->sock) {
		sock_release(socs_mul->sock);
	}

	kfree(socs_mul);
}

int multicast_send(struct hello_packet *hello)
{
	int len;
	struct socket *sock;
	struct kvec vec;
	struct msghdr msg;
	struct sockaddr_in remote_sa;
	struct sockaddr_in6 remote_sa6;
	static int send_error = 0;
	memset(&msg, 0, sizeof(msg));
	spin_lock_bh(&multicast_lock);

	if (socs_mul->family != IPV4 && socs_mul->family != IPV6) {
		log_warning("%s no multicast dev", __func__);
		spin_unlock_bh(&multicast_lock);
		send_error++;

		if (send_error > 3) {
			socs_mul_localnode_up(NULL);
			send_error = 0;
		}

		return -1;
	}

	sock = socs_mul->sock;

	if (socs_mul->family == IPV6) {
		memset(&remote_sa6, 0, sizeof(remote_sa6));
		remote_sa6.sin6_family = AF_INET6;
		remote_sa6.sin6_addr = socs_mul->multicast_addr.addr6;
		remote_sa6.sin6_port = htons(DPORT);
		msg.msg_name = &remote_sa6;
		msg.msg_namelen = sizeof(remote_sa6);
	} else {
		memset(&remote_sa, 0, sizeof(remote_sa));
		remote_sa.sin_family = AF_INET;
		remote_sa.sin_addr = socs_mul->multicast_addr.addr4;
		remote_sa.sin_port = htons(DPORT);
		msg.msg_name = &remote_sa;
		msg.msg_namelen = sizeof(remote_sa);
	}

	len = sizeof(struct hello_packet) + hello->data_len;

	if (len > 65535) {
		spin_unlock_bh(&multicast_lock);
		log_warning("%s hello too long", __func__);
		return -1;
	}

	vec.iov_base = hello;
	vec.iov_len = len;
	len = kernel_sendmsg(sock, &msg, &vec, 1, len);
	if ( len < 0 )
		log_err(ERR_SOCKET, "%s kernel_sendmsg return [%d]", __func__, len);
	spin_unlock_bh(&multicast_lock);
	return len;
}
